using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using collision_and_rigid;
using game_config;
using game_stuff;
using rogue_chapter_maker;

namespace rogue_game
{
    public class Chapter
    {
        public Dictionary<PointMap, int> MapOnYSlotArray { get; }
        public Dictionary<int, PveMap> MGidToMap { get; }
        public PveMap Entrance { get; }
        public PveMap Finish { get; }
        public int ExtraPassiveNum { get; }
        public int ChapterId { get; }
        private RogueGame InRogueGame { get; }

        private bool PassReported { get; set; }

        public InitChapter GenInitChapterPush(int chapterIdsCount)
        {
            return new InitChapter(chapterIdsCount, GetReachedMapsMGidS());
        }

        public PushChapterGoNext GenNewChapterMap()
        {
            var genChapterTuples = GenChapterTuples(MapOnYSlotArray);
            var ySlotArray = ToYSlotArray(genChapterTuples);
            return new PushChapterGoNext(ySlotArray);
        }

        public ImmutableHashSet<IGameResp> GenLoadChapterMsg(int chapterIdsCount)
        {
            var genLoadChapterMsg = new IGameResp[]
            {
                new GameStart(InRogueGame.RogueGameSetting.Depth), GenNewChapterMap(),
                GenInitChapterPush(chapterIdsCount)
            };
            return genLoadChapterMsg.ToImmutableHashSet();
        }

        public int[] GetReachedMapsMGidS()
        {
            var enumerable = MGidToMap.Where(x => x.Value.IsReached).Select(x => x.Key);
            return enumerable.ToArray();
        }

        public Chapter(Dictionary<int, PveMap> mGidToMap, PveMap entrance, PveMap finish, int extraPassiveNum,
            Dictionary<PointMap, int> mapOnYSlotArray, int chapterId, RogueGame inRogueGame)
        {
            MGidToMap = mGidToMap;
            Entrance = entrance;
            Finish = finish;
            ExtraPassiveNum = extraPassiveNum;
            MapOnYSlotArray = mapOnYSlotArray;
            ChapterId = chapterId;
            InRogueGame = inRogueGame;
            PassReported = false;
        }

#if DEBUG
        public IGameResp SetPass(int callSeat, IGameRequest gameRequest)
        {
            Finish.ForceClear();
            return new QuestOkResult(callSeat, gameRequest);
        }
#endif
        public bool IsPassAndNotReport()
        {
            var isPassAndNotReport = Finish.IsClear && !PassReported;
            if (isPassAndNotReport)
            {
                PassReported = true;
            }

            return isPassAndNotReport;
        }


        public static (Chapter genByConfig, YSlot[][] ySlotArray) GenChapterById(int id,
            Random random, RogueGame rogueGame, bool isZip)
        {
            rogueGame.ChapterProcessingRate();
            return CommonConfig.Configs.rogue_game_chapters.TryGetValue(id, out var chapter)
                ? GenByConfig(chapter, random, rogueGame, isZip)
                : throw new KeyNotFoundException();
        }

        public static (int[] creep, int[] boss) ChooseNpcId(MapType mapType, rogue_game_chapter gameChapter,
            Random random)
        {
            var chooseRandCanSame = gameChapter.EliteRandomIn.Any()
                ? gameChapter.EliteRandomIn.ChooseRandCanSame(gameChapter.SmallEliteNum, random)
                : ImmutableArray<int>.Empty;
            var smallCreep = gameChapter.CreepRandIn.Any()
                ? gameChapter.CreepRandIn.ChooseRandCanSame(gameChapter.SmallCreepNum, random).ToList()
                : new List<int>();
            smallCreep.AddRange(chooseRandCanSame);

            var randCanSame = gameChapter.BigEliteRandomIn.Any()
                ? gameChapter.BigMapResRandIn.ChooseRandCanSame(gameChapter.BigEliteNum, random)
                : ImmutableArray<int>.Empty;
            var bigCreep = gameChapter.BigCreepRandIn.Any()
                ? gameChapter.BigCreepRandIn.ChooseRandCanSame(gameChapter.BigCreepNum, random).ToList()
                : new List<int>();
            bigCreep.AddRange(randCanSame);
#if DEBUG
            Console.Out.WriteLine(
                $"small creep{gameChapter.SmallCreepNum + gameChapter.SmallEliteNum} | big creep {gameChapter.BigCreepNum + gameChapter.BigEliteNum}");
            Console.Out.WriteLine($"so small {smallCreep.Count()}  big {bigCreep.Count()}");
#endif
            return mapType switch
            {
                MapType.BigStart => (new int[] { }, new int[] { }),
                MapType.BigEnd => (bigCreep.ToArray(),
                    gameChapter.BigBossCreepRandIn.ChooseRandCanSame(1, random).ToArray()),

                MapType.Small => (smallCreep.ToArray(), new int[] { }),
                MapType.Big => (bigCreep.ToArray(), new int[] { }),

                MapType.SmallStart => (new int[] { }, new int[] { }),
                MapType.SmallEnd => (smallCreep.ToArray(),
                    gameChapter.SmallBossCreepRandIn.ChooseRandCanSame(1, random).ToArray()),
                MapType.Vendor => (new int[] { }, new int[] { }),
                MapType.Hangar => (new int[] { }, new int[] { }),
                _ => throw new ArgumentOutOfRangeException(nameof(mapType), mapType, null)
            };
        }

        public static int[] ChooseInts(MapType mapType, rogue_game_chapter gameChapter)
        {
            return mapType switch
            {
                MapType.BigStart => gameChapter.StartRandIn,
                MapType.BigEnd => gameChapter.BigMapResRandIn,
                MapType.Small => gameChapter.SmallMapResRandIn,
                MapType.Big => gameChapter.BigMapResRandIn,
                MapType.SmallStart => gameChapter.StartRandIn,
                MapType.SmallEnd => gameChapter.SmallMapResRandIn,
                MapType.Vendor => gameChapter.VendorMapResRandIn,
                MapType.Hangar => gameChapter.HangarMapResRandIn,
                _ => throw new ArgumentOutOfRangeException(nameof(mapType), mapType, null)
            };
        }

        public static ChapterTuple[] GenChapterTuples(
            Dictionary<PointMap, int> charMapTop)
        {
            var valueTuples = charMapTop.Select(x => new ChapterTuple(x.Key.MapType, x.Key.Slot, x.Value)).ToArray();
            return valueTuples;
        }

        public static YSlot[][] ToYSlotArray(
            ChapterTuple[] valueTuples)
        {
            var maxX = valueTuples.Select(tuple => tuple.Slot.X).Max();
            var minX = valueTuples.Select(tuple => tuple.Slot.X).Min();
            var enumerableX = Enumerable.Range(minX, maxX - minX + 1);
            var list = valueTuples.GroupBy(t => t.Slot.Y).ToList();
            list.Sort((x, y) => x.Key.CompareTo(y.Key));
            var enumerable = list.Select(x =>
                    x.Select(xx => new YSlot(xx.Slot.X, xx.MapType, xx.MgId))
                        .ToArray())
                ;
            return enumerable.ToArray();
        }

        public static MapTypeInfo[][] YSlotsToMapTypeMatrix(
            YSlot[][] list)
        {
            var selectMany = list.SelectMany(x => x.Select(xx => xx.X)).ToArray();
            var maxX = selectMany.Max();
            var minX = selectMany.Min();
            var enumerableX = Enumerable.Range(minX, maxX - minX + 1);


            var enumerable = list.Select(x =>
            {
                var dictionary = x.ToDictionary(p => p.X, p => new MapTypeInfo(p.MapType, p.MGid));

                return enumerableX.Select(xx => dictionary.TryGetValue(xx, out var mapType)
                        ? mapType
                        : new MapTypeInfo(MapType.Nothing, -1))
                    .ToArray();
            }).ToArray();
            return enumerable;
        }

        public static Chapter GenBySave(Dictionary<PointMap, int> dictionary, rogue_game_chapter gameChapter,
            Random random, IEnumerable<PlayGroundSaveData> playGroundSaveDataS, IEnumerable<int> clearMapIds,
            RogueGame rogueGame)
        {
            var playGroundSaveDic = playGroundSaveDataS.ToDictionary(x => x.MGid, x => x);
            var pveEmptyMaps = dictionary.ToDictionary(pair => pair.Value, pair =>
                {
                    var pointMap = pair.Key;
                    var gMid = pair.Value;
                    var chooseInts = ChooseInts(pointMap.MapType, gameChapter);
                    var tryGetValue = playGroundSaveDic.TryGetValue(gMid, out var save) && save != null;
                    var resId = tryGetValue
                        ? save!.ResId
                        : chooseInts.ChooseRandCanSame(1, random).First();
                    var (creep, boss) = ChooseNpcId(pointMap.MapType, gameChapter, random);
                    var selectMany =
                        tryGetValue
                            ? save!.SaleUnitSaves.Select(x =>
                                x.LoadToSaleUnit(rogueGame.RogueGameSetting.MaskPassiveIds))
                            : gameChapter.SaleUnits
                                .SelectMany(x =>
                                    x.Range.ChooseRandCanSame(x.Num, random)
                                        .Select(xx => SaleUnitStandard.GenById(xx,
                                            rogueGame.RogueGameSetting.MaskPassiveIds))
                                );
                    var saleUnits = selectMany as ISaleUnit[] ?? selectMany.ToArray();
                    if (!tryGetValue)
                    {
                        var saleUnitsLength = saleUnits.Length;
                        var shopLackMulti = rogueGame.RogueGameSetting.ShopLackMulti;
                        var num = (int)(saleUnitsLength * (1f - shopLackMulti));
                        saleUnits = saleUnits.ChooseRandDif(num, random).ToArray();
                        foreach (var saleUnit in saleUnits)
                        {
                            saleUnit.AddCost(rogueGame.RogueGameSetting.ShopMoreCoinMulti);
                        }
                    }

                    var contains = clearMapIds.Contains(gMid);
#if DEBUG
                    if (contains)
                    {
                        Console.Out.WriteLine($"have been reach & clear {gMid}");
                    }

#endif
                    return PveMap.GenEmptyPveMap(pointMap, resId, gMid, creep, boss, rogueGame, saleUnits, contains);
                }
            );
            var start = dictionary.Keys.FirstOrDefault(x =>
                x.MapType == MapType.BigStart || x.MapType == MapType.SmallStart);
            var end = dictionary.Keys.FirstOrDefault(x => x.MapType == MapType.BigEnd || x.MapType == MapType.SmallEnd);

            var startMap = Converter(start, dictionary, pveEmptyMaps);
            var endMap = Converter(end, dictionary, pveEmptyMaps);
            AddTeleports(dictionary, pveEmptyMaps);

            var genByConfig = new Chapter(pveEmptyMaps, startMap, endMap, gameChapter.ExtraPassiveNum, dictionary,
                gameChapter.id, rogueGame);
            return genByConfig;
        }

        public static (Chapter genByConfig, YSlot[][] ySlotArray) GenByConfig(rogue_game_chapter gameChapter,
            Random random, RogueGame rogueGame, bool isZip)
        {
            var chapterMapTop = ChapterMapTop.GenAChapterTopByConfig(gameChapter, isZip);
#if DEBUG
            Console.Out.WriteLine($"{chapterMapTop}");
#endif
            var pointMaps = chapterMapTop.PointMaps.ToList();
            var dictionary = new Dictionary<PointMap, int>();

            for (var i = 0; i < pointMaps.Count; i++)
            {
                dictionary[pointMaps[i]] = i;
            }

            var genChapterTuples = GenChapterTuples(dictionary);
            var ySlotArray = ToYSlotArray(genChapterTuples);

            var pveEmptyMaps = dictionary.ToDictionary(pair => pair.Value,
                pair => GenAEmptyPveMap(gameChapter, random, rogueGame, pair));
            var start = dictionary.Keys.FirstOrDefault(x =>
                x.MapType == MapType.BigStart || x.MapType == MapType.SmallStart);
            var end = dictionary.Keys.FirstOrDefault(x => x.MapType == MapType.BigEnd || x.MapType == MapType.SmallEnd);
            var startMap = Converter(start, dictionary, pveEmptyMaps);
            var endMap = Converter(end, dictionary, pveEmptyMaps);


            // add teleport
            AddTeleports(dictionary, pveEmptyMaps);

            var genByConfig = new Chapter(pveEmptyMaps, startMap, endMap, gameChapter.ExtraPassiveNum, dictionary,
                gameChapter.id, rogueGame);
            return (genByConfig, ySlotArray);
        }

        private static PveMap GenAEmptyPveMap(rogue_game_chapter gameChapter, Random random, RogueGame rogueGame,
            KeyValuePair<PointMap, int> pair)
        {
            var pointMap = pair.Key;
            var value = pair.Value;
            var chooseInts = ChooseInts(pointMap.MapType, gameChapter);
            var next = chooseInts.ChooseRandCanSame(1, random).First();
            var (creep, boss) = ChooseNpcId(pointMap.MapType, gameChapter, random);
            var selectMany = new List<ISaleUnit>();
            if (pointMap.MapType != MapType.Vendor)
                return PveMap.GenEmptyPveMap(pointMap, next, value, creep, boss, rogueGame, selectMany);
            foreach (var gameChapterSaleUnit in gameChapter.SaleUnits)
            {
                var range = gameChapterSaleUnit.Range;
                var i = gameChapterSaleUnit.Num;
                var chooseRandCanSame = range.ChooseRandCanSame(i, random).ToArray();
                var enumerable = chooseRandCanSame.Select(xx =>
                    SaleUnitStandard.GenById(xx, rogueGame.RogueGameSetting.MaskPassiveIds));
                selectMany.AddRange(enumerable);
            }


#if DEBUG
                    Console.Out.WriteLine(
                        $"creep {creep.Aggregate("", (s, x) => s + x + ",")} and boss {boss.Aggregate("", (s, x) => s + x + ",")}");
#endif

            var saleUnitsLength = selectMany.Count;
            var shopLackMulti = rogueGame.RogueGameSetting.ShopLackMulti;
            var num = (int)(saleUnitsLength * shopLackMulti);
            if (num <= 0) return PveMap.GenEmptyPveMap(pointMap, next, value, creep, boss, rogueGame, selectMany);

            var saleUnits = selectMany.Where(x =>
            {
                var titleGood = x.GetTitleGood();
                if (titleGood == null) return false;
                return titleGood.GetTitle() != ContainType.ReRollC;
            });
            var chooseRandDif = saleUnits.ToList().ChooseRandDif(num, random);
            var except = selectMany.Except(chooseRandDif).ToArray();
            foreach (var saleUnit in except)
            {
                saleUnit.AddCost(rogueGame.RogueGameSetting.ShopMoreCoinMulti);
            }


            return PveMap.GenEmptyPveMap(pointMap, next, value, creep, boss, rogueGame, except);
        }

        private static void AddTeleports(Dictionary<PointMap, int> dictionary,
            IReadOnlyDictionary<int, PveMap> pveEmptyMaps)
        {
            foreach (var pointMap in dictionary.Keys)
            {
                var value = Converter(pointMap, dictionary, pveEmptyMaps);

                var applyDevices = pointMap.Links.GroupBy(x => x.Side).Select(pointMapLink =>
                {
                    var linkTo = pointMapLink.FirstOrDefault()?.LinkTo
                                 ?? throw new IndexOutOfRangeException(
                                     $"{pointMap.Links.Count} : direction {pointMapLink.Key} {pointMap.Slot.X}| {pointMap.Slot.Y}");
                    var direction = pointMapLink.Key;
                    var playGroundResMId = value.PlayGround.ResMId;


                    var fromPt = Func(playGroundResMId, direction);

                    var toPlayground = pveEmptyMaps[dictionary[linkTo]].PlayGround;
                    var groundResMId = toPlayground.ResMId;
                    var toPt = Func(groundResMId, direction.OppositeDirection());

                    var teleportUnit = new TeleportUnit(toPlayground, toPt, linkTo.MapType.AllowSizes());
                    var applyDevice = new ApplyDevice(teleportUnit, fromPt, false);
                    return applyDevice;
                });
                value.PlayGround.AddRangeMapInteractable(applyDevices);
            }
        }

        private static TwoDPoint Func(int id, direction direct)
        {
            return StuffLocalConfig.PerLoadMapTransPort.TryGetValue(id, out var dd)
                ? dd.TryGetValue(direct, out var twoDp)
                    ? twoDp.FirstOrDefault() ?? throw new IndexOutOfRangeException()
                    : throw new KeyNotFoundException()
                : throw new KeyNotFoundException();
        }

        private static PveMap Converter(PointMap? x, IReadOnlyDictionary<PointMap, int> dictionary,
            IReadOnlyDictionary<int, PveMap> pveEmptyMaps)
        {
            return dictionary.TryGetValue(x ?? throw new InvalidOperationException(), out var value)
                ? pveEmptyMaps.TryGetValue(value, out var map) ? map : throw new KeyNotFoundException()
                : throw new KeyNotFoundException();
        }

        public ChapterSave Save()
        {
            var keyValuePairs = MGidToMap
                .Where(x => x.Value.IsClear && x.Value.IsReached).ToArray();
            var enumerable = keyValuePairs
                .Select(x => x.Key)
                .ToArray();
            var valuePairs = MapOnYSlotArray
                .Where(x => x.Key.MapType == MapType.Vendor
                            || x.Key.MapType == MapType.Hangar).ToArray();

            var ints = valuePairs.Select(x => x.Value).ToArray();

            var pveMaps = ints.Select(x => MGidToMap[x]).ToArray();
            var playGroundSaveDataS = pveMaps.Select(x => PlayGroundSaveData.GroundSaveData(x.PlayGround)).ToArray();

            var chapterSave =
                new ChapterSave(playGroundSaveDataS, MapOnYSlotArray, enumerable, ChapterId);
            return chapterSave;
        }
    }

    public readonly struct YSlot
    {
        public int X { get; }
        public MapType MapType { get; }
        public int MGid { get; }

        public YSlot(int x, MapType mapType, int mGid)
        {
            X = x;
            MapType = mapType;
            MGid = mGid;
        }
    }

    public readonly struct ChapterTuple
    {
        public MapType MapType { get; }
        public Slot Slot { get; }
        public int MgId { get; }

        public ChapterTuple(MapType mapType, Slot slot, int mgId)
        {
            MapType = mapType;
            Slot = slot;
            MgId = mgId;
        }
    }

    public readonly struct MapTypeInfo
    {
        public MapType MapType { get; }
        public int MgId { get; }

        public MapTypeInfo(MapType mapType, int mgId)
        {
            MapType = mapType;
            MgId = mgId;
        }
    }
}